package com.swak.fx.support;

import static java.util.ResourceBundle.getBundle;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.MissingResourceException;
import java.util.Optional;
import java.util.ResourceBundle;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.swak.config.setting.Settings;
import com.swak.exception.RenderPageException;
import com.swak.fx.support.ui.UIController;
import com.swak.utils.StringUtils;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.stage.StageStyle;
import lombok.Getter;

/**
 * FXML  -> 界面
 * 
 * @author 618lf
 */
public class FXMLRender {

	static final Logger LOGGER = LoggerFactory.getLogger(FXMLRender.class);

	private final UIController controller;
	private final Class<?> theClass;
	private final Settings settings;
	private final Optional<ResourceBundle> bundle;
	private final URL resource;
	@Getter
	private final FXMLView annotation;
	private final FXMLLoader fxmlLoader;
	private final String fxmlRoot;

	public FXMLRender(UIController controller, Settings settings) {
		this.controller = controller;
		this.theClass = this.findFXMLViewClass(controller.getClass());
		this.settings = settings;
		this.fxmlRoot = "/" + theClass.getPackage().getName().replace('.', '/') + "/";
		this.annotation = theClass.getAnnotation(FXMLView.class);
		this.resource = getURLResource();
		this.bundle = getResourceBundle(getBundleName());
		this.fxmlLoader = this.loadSynchronously();
	}

	private Class<?> findFXMLViewClass(Class<?> theClass) {
		if (theClass.getAnnotation(FXMLView.class) != null) {
			return theClass;
		}
		Class<?> supClass = theClass.getSuperclass();
		while (supClass != null && supClass != Object.class) {
			if (supClass.getAnnotation(FXMLView.class) != null) {
				return supClass;
			}
			supClass = supClass.getSuperclass();
		}
		throw new RenderPageException(theClass.getName() + " add @FXMLView!");
	}

	/**
	 * 形成 -- 界面
	 * 
	 * @return
	 */
	public Parent render() {
		try {
			Parent parent = fxmlLoader.getRoot();
			addCSSIfAvailable(parent);
			addStyleIfAvailable(parent);
			return parent;
		} catch (Exception e) {
			LOGGER.error("render page error,", e);
			throw new RenderPageException(e);
		}
	}

	/**
	 * 界面标题
	 * 
	 * @return
	 */
	public String getTitle() {
		String defaultTitle = StringUtils.defaultIfBlank(settings.getConfig("settings.title"),
				Display.getStage().getTitle());
		return StringUtils.defaultIfBlank(defaultTitle, annotation.title());
	}

	/**
	 * Stage 样式
	 * 
	 * @return
	 */
	public StageStyle getStageStyle() {
		String style = annotation.stageStyle();
		return StageStyle.valueOf(style.toUpperCase());
	}

	private void addStyleIfAvailable(final Parent parent) {
		addStyleFromAnnotation(parent);
		String theme = this.settings.getConfig("settings.theme");
		if (StringUtils.isNotBlank(theme)) {
			String[] themes = theme.split(",");
			for (String the : themes) {
				if (StringUtils.isNotBlank(the)) {
					parent.getStyleClass().add(the);
				}
			}
		}
	}

	private void addStyleFromAnnotation(final Parent parent) {
		if (annotation != null && annotation.theme().length > 0) {
			for (final String cssFile : annotation.theme()) {
				parent.getStyleClass().add(cssFile);
			}
		}
	}

	private void addCSSIfAvailable(final Parent parent) {
		addCSSFromAnnotation(parent);
		final URL uri = this.theClass.getResource(getStyleSheetName());
		if (uri == null) {
			return;
		}
		final String uriToCss = uri.toExternalForm();
		parent.getStylesheets().add(uriToCss);
	}

	private String getStyleSheetName() {
		return this.fxmlRoot + getConventionalName(".css");
	}

	private void addCSSFromAnnotation(final Parent parent) {
		if (annotation != null && annotation.css().length > 0) {
			for (final String cssFile : annotation.css()) {
				final URL uri = this.theClass.getResource(cssFile);
				if (uri != null) {
					final String uriToCss = uri.toExternalForm();
					parent.getStylesheets().add(uriToCss);
				}
			}
		}
	}

	private FXMLLoader loadSynchronously() throws IllegalStateException {
		final FXMLLoader loader = new FXMLLoader(resource, bundle.orElse(null));
		try {
			loader.setController(controller);
			loader.load();
		} catch (final IOException | IllegalStateException e) {
			LOGGER.error("render page {} error,", theClass.getName(), e);
			throw new RenderPageException(e);
		}
		return loader;
	}

	private String getBundleName() {
		if (Strings.isNullOrEmpty(annotation.bundle())) {
			final String lbundle = this.theClass.getPackage().getName() + "." + getConventionalName();
			return lbundle;
		}

		final String lbundle = annotation.bundle();
		return lbundle;
	}

	private Optional<ResourceBundle> getResourceBundle(final String name) {
		try {
			return Optional.of(getBundle(name, new ResourceBundler(getResourceBundleCharset())));
		} catch (final MissingResourceException ex) {
			return Optional.empty();
		}
	}

	private Charset getResourceBundleCharset() {
		return Charset.forName(annotation.encoding());
	}

	private URL getURLResource() {
		if (annotation != null && !annotation.value().equals("")) {
			return this.theClass.getResource(annotation.value());
		} else {
			return this.theClass.getResource(getFxmlPath());
		}
	}

	final String getFxmlPath() {
		final String fxmlPath = fxmlRoot + getConventionalName(".fxml");
		return fxmlPath;
	}

	private String getConventionalName(final String ending) {
		return getConventionalName() + ending;
	}

	private String getConventionalName() {
		return stripEnding(this.theClass.getSimpleName().toLowerCase());
	}

	private static String stripEnding(final String clazz) {
		if (!clazz.endsWith("view")) {
			return clazz;
		}
		return clazz.substring(0, clazz.lastIndexOf("view"));
	}
}